{
  "cells": [
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "afc6e548-9dec-4755-9fbd-3b8ac0d3afe7",
      "metadata": {},
      "source": [
        "# Introduction to Sessions\n",
        "In this notebook, we'll get used to working with sessions in Azure Quantum by using a session to run multiple Qiskit jobs on a target."
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "21a1bb4e-c94f-483d-b3e8-3e0ea7affb92",
      "metadata": {
        "nteract": {
          "transient": {
            "deleting": false
          }
        }
      },
      "source": [
        "## What is a session?\n",
        "A session is a logical grouping of one or more jobs submitted to a single target (backend). Each session has a unique ID that is attached to all jobs in that session. While sessions can be used broadly in Azure Quantum, they are particularly helpful for complex hybrid algorithms where classical code is interwoven with a large number of quantum jobs. With sessions you can focus on your algorithm as a whole instead of managing individual quantum jobs.\n",
        "\n",
        "We'll use Qiskit to submit jobs to the IonQ simulator backend for this example, but you can use sessions with Q# + Python and Cirq, and variety of backends. See the [sessions documentation](https://aka.ms/AQ/Hybrid/Sessions/Docs) for details."
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "27f2651c-53f4-4751-bb95-7b649c4fd87d",
      "metadata": {
        "nteract": {
          "transient": {
            "deleting": false
          }
        }
      },
      "source": [
        "### 1. Connect to Azure Quantum and build a quantum circuit\n",
        "\n",
        "Before we create a session, we'll construct an instance of the `AzureQuantumProvider` and select a backend."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "98381b91-f3a6-47bc-b4c6-2037fdb9c46f",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Connect to the Azure Quantum workspace\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.tools.monitor import job_monitor\n",
        "from azure.quantum.qiskit import AzureQuantumProvider\n",
        "\n",
        "provider = AzureQuantumProvider (\n",
        "            resource_id = \"\",\n",
        "            location = \"\")\n",
        "\n",
        "ionq_sim = provider.get_backend('ionq.simulator')\n",
        "quantinuum_sim = provider.get_backend('quantinuum.sim.h1-1e')\n",
        "rigetti_sim = provider.get_backend('rigetti.sim.qvm')\n",
        "\n",
        "# Set the backend you want to use here.\n",
        "# WARNING: Quantinuum simulator usage is not unlimited. Running this sample against it could consume a significant amount of your eHQC quota.\n",
        "backend = ionq_sim"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "023d62e2-e7aa-45d1-95ec-9cffef7d1148",
      "metadata": {},
      "source": [
        "For this example, we'll create a simple 2-qubit circuit that we will run multiple times in a session. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c2a96a3d-eafe-4868-914b-b0b41ec95b9d",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Create a quantum circuit acting on two qubits\n",
        "circuit = QuantumCircuit(2, 2)\n",
        "circuit.name = \"GenerateRandomBit\"\n",
        "circuit.h(0)\n",
        "circuit.cnot(0,1)\n",
        "circuit.measure([0,1], [0,1])\n",
        "\n",
        "# Print out the circuit\n",
        "circuit.draw()"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "6849deeb-1651-4cd1-beaf-f1b3ba0e9158",
      "metadata": {
        "nteract": {
          "transient": {
            "deleting": false
          }
        }
      },
      "source": [
        "### 2. Run quantum jobs in a session\n",
        "Now it's time to create a session! We open a session using a ```with``` statement so the session is automatically closed upon completion of our program. Then we run as many individual quantum jobs within the session as we need to, in this case we'll submit our circuit three times. You can also add classical code between individual quantum job submissions within the session."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8852db76-1d86-4937-8b83-5ddfc91e1b62",
      "metadata": {
        "jupyter": {
          "outputs_hidden": false,
          "source_hidden": false
        },
        "nteract": {
          "transient": {
            "deleting": false
          }
        }
      },
      "outputs": [],
      "source": [
        "with backend.open_session(name=\"Qiskit Session\") as session:\n",
        "    job1 = backend.run(circuit=circuit, shots=100, job_name=\"Job 1\") # First job submission\n",
        "    job_monitor(job1)\n",
        "    # Classical code could go here\n",
        "    job2 = backend.run(circuit=circuit, shots=100, job_name=\"Job 2\") # Second job submission\n",
        "    job_monitor(job2)\n",
        "    # Classical code could go here\n",
        "    job3 = backend.run(circuit=circuit, shots=100, job_name=\"Job 3\") # Third job submission\n",
        "    job_monitor(job3)"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "f1568365-24e7-47ce-b603-4959352ecf88",
      "metadata": {
        "nteract": {
          "transient": {
            "deleting": false
          }
        }
      },
      "source": [
        "And that's it! You've run your first program with sessions! 🥳🎉🎊\n",
        "\n",
        "Running our simple program within a session lets us better organize and manage the jobs. For example, we can list all jobs in the session:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "1ba2ff03-4b0a-4483-bcf5-e79f1c8c5132",
      "metadata": {
        "jupyter": {
          "outputs_hidden": false,
          "source_hidden": false
        },
        "nteract": {
          "transient": {
            "deleting": false
          }
        }
      },
      "outputs": [],
      "source": [
        "session_jobs = session.list_jobs()\n",
        "[session_job.details.name for session_job in session_jobs]"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "11abdd30-4bca-4f3f-87ce-f75d7cd6ff09",
      "metadata": {
        "nteract": {
          "transient": {
            "deleting": false
          }
        }
      },
      "source": [
        "You also can view your session in the Job management blade of your Quantum Workspace in the Azure Portal. Instead of seeing individual jobs immediately, you will see the session instead, and can click on the session to drill down into the individual jobs. \n",
        "\n",
        "This may be more organization than we need for our simple 3 quantum jobs program, but the ability to view and manage jobs at the session level can be a big help as you start submitting multiple runs of complex hybrid algorithms that can countain hundreds or thousands of individual jobs."
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "a2810603-7b15-4b44-91f1-5afcf4ae1e02",
      "metadata": {},
      "source": [
        "### 3. Change a session's job failure policy\n",
        "\n",
        "Many parameterized hybrid algorithms are sensitive to individual quantum job failures: if one job fails, the correct parameters for subsequent jobs may not be properly set. This can lead to situations where improperly parameterized jobs continue to be submitted to targets, wasting valuable time and resources.\n",
        "\n",
        "To prevent this, job failures cause sessions to be closed by default. The Azure Quantum service attempts to cancel any pending jobs already submitted to the target, and new jobs submitted to the closed session are rejected.\n",
        "\n",
        "If you'd like to continue submitting new jobs within a session even if a previous job fails, you can change this default behavior by specifying a job failure policy of ```job_failure_policy=SessionJobFailurePolicy.CONTINUE``` when creating the session:\n",
        "\n",
        "```python\n",
        "with backend.open_session(name=\"Qiskit Session\", job_failure_policy=SessionJobFailurePolicy.CONTINUE) as session:\n",
        "   job1 = backend.run(circuit=circuit, shots=100, job_name=\"Job 1\") # First job submission\n",
        "   ...\n",
        "```"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "286889c4-94c3-4623-9ec5-7516cbb0103f",
      "metadata": {
        "nteract": {
          "transient": {
            "deleting": false
          }
        }
      },
      "source": [
        "### 4. Next steps\n",
        "You now have everything you need to get starting building quantum algorithms with sessions! As a next step, you can explore advanced samples in the hybrid quantum computing sample gallery, including an example of using sessions to run the Variational Quantum Eigensolver to estimate the ground state energy of a hydrogen molecule. As you create more complex algorithms with extended runtime, we recommend [running them locally](https://learn.microsoft.com/en-us/azure/quantum/how-to-long-running-experiments#local-development) to avoid job failures caused by occasional connectivity issues with Azure Quantum's hosted notebooks.\n",
        "\n",
        "For more information on sessions, including how Quantinuum provides **reserved QPU access** once a session is started, check out the [sessions documentation](https://aka.ms/AQ/Hybrid/Sessions/Docs).\n",
        "\n",
        "We can't wait to see what you build!\n",
        "\n"
      ]
    }
  ],
  "metadata": {
    "kernel_info": {
      "name": "python3"
    },
    "kernelspec": {
      "display_name": "Python 3 (ipykernel)",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.9.16"
    },
    "microsoft": {
      "host": {
        "AzureQuantum": {
          "sourceLink": "https://raw.githubusercontent.com/microsoft/Quantum/2faada09f683b1112a3730caa561faff7b35b5df/samples/azure-quantum/hello-world/HW-ionq-qiskit.ipynb",
          "sourceType": "SampleGallery"
        }
      }
    },
    "nteract": {
      "version": "nteract-front-end@1.0.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}
